package com.xxl.job.executor.service.jobhandler;

import com.google.common.util.concurrent.*;
import com.ruoyi.bizsys.domain.BatchJobMonitoring;
import com.ruoyi.bizsys.domain.CreditAccount;
import com.ruoyi.bizsys.domain.LoanOffset;
import com.ruoyi.bizsys.service.IBatchJobMonitoringService;
import com.ruoyi.bizsys.service.ILoanOffsetService;
import com.ruoyi.business.processIou.ProcessForIouServiceForExceptionData;
import com.ruoyi.business.service.IBLoanInfoService;
import com.ruoyi.business.service.IBatchDateJobService;
import com.ruoyi.business.service.impl.BLoanInfoServiceImpl;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.executor.service.thread.DailyCutBatchJobThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static com.xxl.job.executor.constants.Constants.MONITOR_COUNTNUMS;

/**
 * 日切批量任务
 */
@Slf4j
@Component
public class DailyCutBatchJob {

    @Autowired
    IBatchDateJobService batchDateJobService;

    @Autowired
    @Lazy
    IBLoanInfoService loanInfoService;

    @Autowired
    IBatchJobMonitoringService batchJobMonitoringService;

    @Autowired
    private ProcessForIouServiceForExceptionData processForIouServiceForExceptionData;

    @Autowired
    ILoanOffsetService loanOffsetService;

    @Autowired
    RedisCache redisCache;

    @XxlJob("dailyCutBatchJobHandler")
    public void execute() {

        String params = XxlJobHelper.getJobParam();
        if (StringUtils.isNotBlank(params)) {
            XxlJobHelper.log("DailyCutBatchJobHandler 入参 >>>>>>>>>> param = {}", params);

            if (StringUtils.equals("-1", params)) {
                runBatchForIou();
                return;
            }
            runBatch(params);
            return;
        }

        XxlJobHelper.log("日切批量任务 Start >>>>>>>>>>");

        // 获取系统最近跑批
        BatchJobMonitoring batchJobMonitoring = batchDateJobService.getBatchDateRecord();
        if (null == batchJobMonitoring) {
            log.info("最近跑批记录为 null");
            throw new BusinessException(RspEnmu.DAILY_CUST_BATCH_TIME_ERROR);
        }

        log.info("最近跑批日为 {}", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, batchJobMonitoring.getBatchDate()));

        // 跑批日相差
        Date nowBatchDate = new Date();
        int dayDiff = DateUtils.daysBetween(batchJobMonitoring.getBatchDate(), nowBatchDate);
        if (dayDiff != 1) {
            log.info("最近一次跑批时间不是昨天");
            throw new BusinessException(RspEnmu.DAILY_CUST_BATCH_TIME_ERROR);
        }

        //查询所有在贷的账户总数(在贷激活日早于跑批日的)
        Date nowDate = getNowDate(nowBatchDate);
        List<CreditAccount> accountList = loanInfoService.getBatchJobOnLoanAccount(DateUtils.dateTime(nowDate));
        int accountCount = accountList.size();
        if (accountCount <= 0) {
            log.info("日切跑批任务没有记录需要执行");
            updateBatchjobMonitoring(nowBatchDate, batchJobMonitoring);
            log.info("日切跑批任务 End >>>>>>>>>> 日切跑批任务没有记录需要执行");
            return;
        }

        redisCache.setCacheObject(BLoanInfoServiceImpl.theardName + MONITOR_COUNTNUMS, accountCount);
        log.info("日切跑批共有 {} 个客户需要执行", accountCount);

        //需要multiple个线程来执行
        int threadStep = 200;//每个线程执行的客户数
        int threadCount = accountCount / threadStep;
        if (threadCount * threadStep != accountCount) {
            threadCount++;
        }

        //线程计数器
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        //线程池大小
        int poolSize = 5;
        ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
        ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);

        String redisName = "DailyCutBatchJob-" + DateUtils.dateTime(nowDate);
        int threadAccountCount = 0;
        while (threadAccountCount != accountCount) {
            //分页取数startNum
            int startLimit = threadAccountCount;

            threadAccountCount += threadStep;
            if (threadAccountCount > accountCount) {
                threadAccountCount = accountCount;
            }

            String threadRedisName = redisName + ":" + startLimit + "-" + threadAccountCount;
            List<CreditAccount> splitAccountList = accountList.subList(startLimit, threadAccountCount);
            redisCache.setCacheList(threadRedisName, splitAccountList);
            redisCache.expire(threadRedisName, 23, TimeUnit.HOURS);

            DailyCutBatchJobThread thread = new DailyCutBatchJobThread("[DailyCutBatchJobThread-" + startLimit + "]", nowDate, threadRedisName, loanInfoService, redisCache);
            ListenableFuture listenableFuture = listeningExecutorService.submit(thread);
            Futures.addCallback(listenableFuture, new FutureCallback<String>() {
                @Override
                public void onSuccess(String result) {
                    countDownLatch.countDown();
                    XxlJobHelper.log("[" + this.getClass().getName() + "]线程处理结果：" + result);
                }

                @Override
                public void onFailure(Throwable throwable) {
                    countDownLatch.countDown();
                    XxlJobHelper.log("[" + this.getClass().getName() + "]线程处理出错：" + throwable);
                }
            });
        }

        boolean result = true;
        try {
            // 超过3个小时放弃
            countDownLatch.await(3 * 60, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            result = false;
            XxlJobHelper.handleFail("日切跑批执行失败，非正常结束");
        }

        //日切
        if (result) {
            updateBatchjobMonitoring(nowBatchDate, batchJobMonitoring);
        }

        XxlJobHelper.log("日切批量任务 End >>>>>>>>>>");
    }

    /**
     * 单个数据
     *
     * @param custNo
     */
    public void runBatch(String custNo) {
        XxlJobHelper.log("单客户罚息处理任务 Start >>>>>>>>>> custNo = {}", custNo);

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date nowDate = calendar.getTime();
        loanInfoService.doBatchJobByCust(nowDate, loanInfoService.getOneOnLoanAccountAll(DateUtils.dateTime(nowDate), custNo));

        XxlJobHelper.log("单客户罚息处理任务 End >>>>>>>>>> custNo = {}", custNo);
    }

    /**
     * 处理异常数据 LateFee 改成-1即可重新回溯
     */
    public void runBatchForIou() {
        XxlJobHelper.log("回溯 Offset late=-1 任务 Start >>>>>>>>>>");

        // LateFee = -1的
        List<LoanOffset> list = loanOffsetService.selectLoanOffsetListByLateFee();
        log.info("处理异常数据offset_:list.size:{}", list.size());
        if (!CollectionUtils.isEmpty(list)) {
            for (int i = 0; i < list.size(); i++) {
                log.info("处理异常数据offset_:getLoanSerialNo:{}", list.get(i).getLoanSerialNo());
                processForIouServiceForExceptionData.initForOne(list.get(i).getLoanSerialNo());
            }
        }
        XxlJobHelper.log("回溯 Offset late=-1 任务 End >>>>>>>>>>");
    }

    private Date getNowDate(Date nowBatchDate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(nowBatchDate);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date nowDate = calendar.getTime();
        return nowDate;
    }

    private void updateBatchjobMonitoring(Date nowBatchDate, BatchJobMonitoring batchJobMonitoring) {
        batchJobMonitoring.setBatchDate(nowBatchDate);
        batchJobMonitoringService.updateBatchJobMonitoring(batchJobMonitoring);
    }
}

